/*
 * @(#)AdviceFunctions.java 1.0 2000/12/16
 * 
 * Copyright 2000 Thomas Barnekow. All Rights Reserved.
 * 
 * This software is the proprietary information of Thomas Barnekow. Use is
 * subject to license terms.
 */

package jessx;

import jess.*;

import jessx.util.*;
import tb.test.*;

/**
 * This {@link jess.Userpackage}defines a set of functions leveraging the
 * possibilities to advise a {@link jess.Userfunction}(see
 * {@link <a href="http://herzberg.ca.sandia.gov/jess/docs/functions.html#defadvice">defadvice</a>}
 * and
 * {@link <a href="http://herzberg.ca.sandia.gov/jess/docs/functions.html#undefadvice">undefadvice</a>})
 * and to otherwise rewrite, or reinterpret, constructs (e.g.,
 * {@link jess.Funcall}and {@link jess.Fact}) in Jess.
 * <p>
 * Advising functions is the official way to "rewrite" functions, i.e., by
 * somehow transforming the function's argument vector. See section "
 * {@link <a href="http://herzberg.ca.sandia.gov/jess/docs/language.html#defadvice">2.5 Defadvice</a>}"
 * of the Jess Language Reference for a detailed explanation and some examples.
 * Rewriting a construct means reinstantiating it as a construct of a different
 * type. For example, a {@link jess.Fact}can be rewritten as a
 * {@link jess.Funcall}and vice-versa.
 * <p>
 * Let's look at an example: the {@link jessx.ProviderFunctions fsm-select}
 * function takes one or more goal facts <code>(need-<i>actual-fact</i>)</code>
 * as arguments and returns a list of facts. Now imagine a database schema
 * "scott" containing the "emp" table, whose records are mapped to
 * corresponding <code>(emp)</code> facts, e.g., <code><ul>
 * (emp (empno 1) (ename "Thomas") (dept 4711))
 * </ul></code> When backward chaining is activated for these facts there is also a
 * backward chaining goal <code>(need-emp)</code> having the same structure
 * as <code>(emp)</code>. Usually, you'd write <code><ul>
 * (bind ?goal (assert (need-emp))) ; assert (need-emp (empno nil) ...) <br>
 * (bind $?facts (fsm-select ?goal)) ; call {@link jessx.provider.FactReader} <br>
 * (foreach ?fact $?facts (assert ?fact)) ; assert each fact in the list <br>
 * (retract ?goal) ; dispose of goal fact
 * </ul></code> in order to select all emp records from your database and to assert
 * them as facts for further processing. On the other hand, using the expand
 * function provided by this package you can equivalently write <code><ul>
 * (expand (assert (fsm-select (need-emp))))
 * </ul></code> This is shorter and, more importantly, you don't need to assert a
 * goal fact first. As a consequence, the Rete network will not be affected,
 * which can save some processing time.
 * <p>
 * By using the defadvice function this can be reduced even further. Once you
 * have executed <code><ul>
 * (expand-function assert)
 * </ul></code> which is shorthand for <code><ul>
 * (defadvice before assert (delegate-advice expand))
 * </ul></code> you can write <code><ul>
 * (assert (fsm-select (need-emp)))
 * </ul></code> thereafter. Very short, isn't it ;-)
 * <p>
 * What does the expand function do? When the Jess parser looks at the last two
 * <code>assert</code> function calls it interprets <code>(fsm-select (need-emp))</code>
 * as an ordered fact and <code>(need-emp)</code> as a function call (if
 * there is no <code>need-emp</code> function this results in an error
 * message). But we know better: <code>fsm-select</code> is a function and
 * <code>(need-emp)</code> looks like one of our ordered goal facts. Thus,
 * the expand function first rewrites the constructs as follows: It
 * reinterprets the "false" <code>(need-emp)</code> {@link jess.Funcall}as
 * an ordered {@link jess.Fact}and the alleged <code>(fsm-select (need-emp))</code>
 * {@link jess.Fact}as a {@link jess.Funcall}with a single argument of type
 * {@link jess.Fact}. Next, the {@link jess.Funcall}is executed, yielding a
 * {@link jess.ValueVector}containing a number of {@link jess.Fact}objects.
 * Since most functions, specifically assert or retract, don't accept lists (or
 * multifield values) the expand function "flattens"
 * <ul>
 * <code>(assert &lt;list-value&gt;)</code>
 * </ul>
 * yielding
 * <ul>
 * <code>(assert &lt;element-1&gt; ... &lt;element-n&gt;)</code>
 * </ul>
 * where each &lt;element-i&gt; is a {@link jess.Fact}. Executing the <code>assert</code>
 * {@link jess.Funcall}and thereby asserting each fact in the list is done
 * during the last step. If the expand function is called explicitly, it does
 * this itself. On the other hand, if it advises the assert function, expand
 * only transforms the argument vector.
 * <p>
 * The interpret function behaves almost exactly like expand. By contrast, it
 * does not flatten lists. It can be used, for example, in order to interpret
 * facts without asserting them: <code><ul>(bind ?fact (interpret (emp (empno 2) (ename "Ernest"))))</ul></code>
 * 
 * Usually, when you advise a function you need to write another function, or
 * function call, transforming either the <code>$?argv</code> or the <code>?retval</code>
 * (multi-)variable. Using the delegate-advice function you can delegate, or
 * dispatch, to any "useful" function. Example: <code><ul>
 * (deffunction client (?x) (printout t ?x crlf)) <i>; function to be advised </i><br>
 * (deffunction plus-one (?x) (+ ?x 1)) <i>; advisor </i><br>
 * (deffunction times-two (?x) (* ?x 2)) <i>; advosor </i><br>
 * <p>
 * (client 10) <i>; no advice, prints 10 </i>
 * <p>
 * (defadvice before client (delegate-advice plus-one)) <br>
 * (client 10) <i>; single advice, prints 11 = 10 + 1</i>
 * <p>
 * (defadvice before client (delegate-advice plus-one times-two)) <br>
 * (client 10) <i>; double (piped) advice, prints 22 = (10 + 1) * 2 </i>
 * </ul></code>
 * 
 * Now, this is the list of functions provided by this {@link jess.Userpackage}:
 * <p>
 * <b><i>(print-argv)</i></b>
 * <ul>
 * Just print the argument vector. This function can only be used as an advisor
 * and is installed by executing <code><ul><br>
 *   (defadvice before &lt;function-name&gt; (print-argv))
 * </ul></code><br>for example, for debugging purposes.
 * </ul>
 * <b><i>(delegate-advice &lt;function-name&gt;+)</i></b><br>
 * <ul>
 * Delegate task to a "normal" function. This function can only be used as an
 * advisor.
 * </ul>
 * <b><i>(interpret &lt;construct&gt;+)</i></b><br>
 * <ul>
 * Reinterpret constructs. Can also be used as a "normal" function.
 * </ul>
 * <b><i>(expand &lt;construct&gt;+)</i></b><br>
 * <ul>
 * Reinterpret constructs and expand lists. Can be used as a "normal" function.
 * </ul>
 * <b><i>(expand-function &lt;function-name&gt;)</i></b><br>
 * <ul>
 * Shorthand for <code><ul><br>
 *   (defadvice before &lt;function-name&gt; (delegate-advice expand))
 * </ul></code><br>
 * </ul>
 * <b><i>(interpret-function &lt;function-name&gt;)</i></b><br>
 * <ul>
 * Shorthand for <code><ul><br>
 *   (defadvice before &lt;function-name&gt; (delegate-advice interpret))
 * </ul></code><br>
 * </ul>
 * 
 * @author Thomas Barnekow
 * @version 1.0
 * 
 * @see <a href="http://herzberg.ca.sandia.gov/jess/docs/functions.html#defadvice">
 *      defadvice</a>
 * @see <a href="http://herzberg.ca.sandia.gov/jess/docs/functions.html#undefadvice">
 *      undefadvice</a>
 */
public class AdviceFunctions implements Userpackage {

	public static final String PRINT_ARGV = "print-argv";

	public static final String INTERPRET = "interpret";
	public static final String EXPAND = "expand";

	public static final String DELEGATE_ADVICE = "delegate-advice";

	public static final String EXPAND_FUNCTION = "expand-function";
	public static final String INTERPRET_FUNCTION = "interpret-function";

	/**
	 * Add functions.
	 */
	public void add(Rete engine) {
		engine.addUserfunction(new PrintArgumentVector()); // PRINT_ARGV
		engine.addUserfunction(new Interpret(false)); // INTERPRET
		engine.addUserfunction(new Interpret(true)); // EXPAND
		engine.addUserfunction(new DelegateAdvice()); // DELEGATE_ADVICE
		engine.addUserfunction(new AdviseFunction(EXPAND_FUNCTION));
		engine.addUserfunction(new AdviseFunction(INTERPRET_FUNCTION));
	}

	/**
	 * Abstract base class for (defadvice) functions
	 */
	abstract class AbstractAdvice implements Userfunction {

		private String m_name;

		protected AbstractAdvice(String name) {
			m_name = name;
		}

		public String getName() {
			return m_name;
		}

		public Value call(ValueVector vvec, Context context)
			throws JessException {
			ValueVector argv = context.getVariable("argv").listValue(context);
			ValueVector result = processArguments(argv, vvec, context);
			context.setVariable("argv", new Value(result, RU.LIST));
			return Funcall.TRUE;
		}

		/**
		 * Transform the argument vector.
		 * 
		 * @param argv
		 *            the $?argv multivariable's value
		 * @param vvec
		 *            this functions's original arguments
		 * @param context
		 *            the call context
		 * @return a "transformed" argument vector
		 * @exception JessException
		 *                in case anything goes wrong
		 */
		abstract ValueVector processArguments(
			ValueVector argv,
			ValueVector vvec,
			Context context)
			throws JessException;
	}

	/**
	 * Shorthand for printing the argument vector.
	 */
	class PrintArgumentVector extends AbstractAdvice {

		PrintArgumentVector() {
			super(PRINT_ARGV);
		}

		ValueVector processArguments(
			ValueVector argv,
			ValueVector vvec,
			Context context) {
			context.getEngine().getOutStream().println(
				argv.toStringWithParens());
			return argv;
		}
	}

	/**
	 * Delegate call to a "normal" function.
	 */
	class DelegateAdvice extends AbstractAdvice {

		DelegateAdvice() {
			super(DELEGATE_ADVICE);
		}

		ValueVector processArguments(
			ValueVector argv,
			ValueVector vvec,
			Context context)
			throws JessException {
			// Strip input argument vector's head
			int argc = argv.size() - 1;
			ValueVector vector = new ValueVector(argc);
			ValueVector.copy(argv, 1, vector, 0, argc);

			// Call delegates in a piped manner
			for (int i = 1; i < vvec.size(); i++) {
				Value value =
					execute(vvec.get(i).atomValue(context), vector, context);
				vector =
					(value.type() == RU.LIST)
						? value.listValue(context)
						: new ValueVector(1).add(value);
			}

			// Build and return result argument vector
			// Do not reuse argv!!!
			ValueVector result = new ValueVector(1 + argc);
			result.add(argv.get(0));
			result.addAll(vector);

			return result;
		}

		/**
		 * Execute named function with given parameter vector.
		 */
		Value execute(String name, ValueVector vector, Context context)
			throws JessException {
			Funcall fc = new Funcall(name, context.getEngine());
			fc.addAll(vector);
			return fc.execute(context);
		}
	}

	/**
	 * Interpret (and expand) {@link jess.ValueVector}s.
	 */
	class Interpret implements Userfunction {

		boolean m_expand;
		String m_name;

		Interpret(boolean expand) {
			m_expand = expand;
			m_name = expand ? EXPAND : INTERPRET;
		}

		public String getName() {
			return m_name;
		}

		public Value call(ValueVector vvec, Context context)
			throws JessException {
			Debug.println("Interpret.call: " + makeString(vvec, context));
			if (vvec.size() < 2) {
				return Funcall.NIL;
			} else if (vvec.size() == 2) {
				return interpret(vvec.get(1), m_expand, context);
			} else {
				ValueVector result =
					appendList(new ValueVector(), vvec, m_expand, 1, context);
				return new Value(result, RU.LIST);
			}
		}
	}

	/**
	 * Convenience function
	 */
	class AdviseFunction implements Userfunction {

		private String m_name;

		AdviseFunction(String name) {
			if (name != EXPAND_FUNCTION && name != INTERPRET_FUNCTION) {
				throw new IllegalArgumentException(
					"Illegal function name: " + name);
			}
			m_name = name;
		}

		public String getName() {
			return m_name;
		}

		public Value call(ValueVector vvec, Context context)
			throws JessException {
			int size = vvec.size();
			if (size < 2)
				return Funcall.FALSE;

			String name = vvec.get(1).atomValue(context);
			if (m_name.equals(EXPAND_FUNCTION))
				adviseFunction(name, context.getEngine(), EXPAND);
			else if (m_name.equals(INTERPRET_FUNCTION))
				adviseFunction(name, context.getEngine(), INTERPRET);
			else
				throw new JessException(
					"AdviseFunction.call",
					"Illegal function name:",
					m_name);

			return Funcall.TRUE;
		}
	}

	/**
	 * Append one value vector to another.
	 */
	public static ValueVector appendList(
		ValueVector target,
		ValueVector source,
		boolean expand,
		int fromIndex,
		Context context)
		throws JessException {
		for (int i = fromIndex; i < source.size(); i++) {
			append(target, source.get(i), expand, context);
		}
		return target;
	}

	/**
	 * Append the value to the vector after interpreting it.
	 * 
	 * @see #interpret
	 */
	public static ValueVector append(
		ValueVector vector,
		Value value,
		boolean expand,
		Context context)
		throws JessException 
	{
		Debug.println("AdviceFunctions.append: " + makeString(value, context));
		Value v = interpret(value, expand, context);
		if (!expand || v.type() != RU.LIST)
			return vector.add(v);
		else
			return appendList(vector, v.listValue(context), expand, 0, context);
	}

	/**
	 * Interpret variables, {@link jess.Fact}s, and {@link jess.Funcall}s.
	 * 
	 * @see #interpretFact
	 * @see #interpretFuncall
	 */
	public static Value interpret(Value value, boolean expand, Context context)
		throws JessException {
		switch (value.type()) {
			case RU.VARIABLE :
			case RU.MULTIVARIABLE :
				return value.resolveValue(context);
			case RU.FACT :
				return interpretFact(value, expand, context);
			case RU.FUNCALL :
				return interpretFuncall(value, expand, context);
			default :
				return value;
		}
	}

	/**
	 * Interpret {@link jess.Fact}s and make sure that a registered
	 * {@link jess.Userfunction}gets interpreted as a function.
	 */
	static Value interpretFact(Value value, boolean expand, Context context)
		throws JessException {
		Rete engine = context.getEngine();
		Fact fact = value.factValue(context);
		String name = JessxTools.getFactName(fact);

		// If it's a fact but there's no registered function we're done
		if (engine.findUserfunction(name) == null) {
			return value;
		}

		// We have found a function that has been interpreted as a fact.
		// We now reinterpret it as a function call
		ValueVector vv = fact.get(0).listValue(context);
		Funcall fc = new Funcall(name, engine);
		appendList(fc, vv, expand, 0, context);

		Debug.println("REINTERPRETED AS FUNCALL: " + fc.toString());

		// If a function call had been compiled as a fact, we need to execute
		// it.
		// Then, we're done :-)
		return fc.execute(context);
	}

	/**
	 * Interpret {@link jess.Funcall}s and make sure that it gets interpreted
	 * as a {@link jess.Fact}, if there is a matching {@link jess.Deftemplate}.
	 */
	static Value interpretFuncall(Value value, boolean expand, Context context)
		throws JessException {
		Rete engine = context.getEngine();
		Funcall fc = (Funcall) value.funcallValue(context);
		// used to clone() it before ...
		String name = fc.get(0).atomValue(context);

		Deftemplate dt = engine.findDeftemplate(name);
		if (dt == null || engine.findUserfunction(name) != null) {
			Funcall f = new Funcall(name, engine);
			appendList(f, fc, expand, 1, context);
			return f.execute(context);
		}

		Fact fact = new Fact(name, engine);
		if (dt.getSlotIndex("__data") >= 0)
			return new FactIDValue(initOrderedFact(fact, fc, context));
		else
			return new FactIDValue(initUnorderedFact(fact, fc, context));
	}

	/**
	 * Create an ordered fact
	 */
	static Fact initOrderedFact(Fact fact, Funcall fc, Context context)
		throws JessException {
		// Set default multislot
		ValueVector vv = new ValueVector();
		for (int i = 1; i < fc.size(); i++) {
			vv.add(fc.get(i).resolveValue(context)); // revisit
		}
		fact.setSlotValue("__data", new Value(vv, RU.LIST));

		Debug.println("INITIALIZED ORDERED FACT: " + fact);

		// Done
		return fact;
	}

	/**
	 * Create an unordered fact
	 */
	static Fact initUnorderedFact(Fact fact, Funcall fc, Context context)
		throws JessException {
		for (int i = 1; i < fc.size(); i++) {
			Funcall slot = fc.get(i).funcallValue(context);
			String slotName = slot.get(0).atomValue(context);
			Value slotValue = slot.get(1).resolveValue(context); // revisit

			fact.setSlotValue(slotName, slotValue);
		}

		Debug.println("INITIALIZED UNORDERED FACT: " + fact);

		return fact;
	}

	/**
	 * Advise a single function. This is basically a shorthand for <b><code>(defadvice before &lt;name&gt; (delegate-advice &lt;advisor&gt;))
	 * 
	 * @name  the function's name
	 * @engine  the Rete engine
	 * @advisor name of advising function
	 */
	public static void adviseFunction(String name, Rete engine, String advisor)
		throws JessException {
		// Define Funcall (delegate-advice <advisor>)
		Funcall advice = new Funcall(AdviceFunctions.DELEGATE_ADVICE, engine);
		advice.add(new Value(advisor, RU.ATOM));

		// Execute (defadvice before <name> (delegate-advice <advisor>))
		Funcall fc = new Funcall("defadvice", engine);
		fc.add(new Value("before", RU.ATOM));
		fc.add(new Value(name, RU.ATOM));
		fc.add(new FuncallValue(advice));
		fc.execute(engine.getGlobalContext());
	}

	/*
	 * Debugging methods
	 */

	/**
	 * Create String representation (for debugging purposes).
	 */
	public static final String makeString(Value value, Context context)
		throws JessException {
		switch (value.type()) {
			case RU.FACT :
				return value.factValue(context).toString();
			case RU.FUNCALL :
				return makeString(value.funcallValue(context), context);
			default :
				return value.toString();
		}
	}

	/**
	 * Create String representation (for debugging purposes).
	 */
	public static final String makeString(ValueVector vvec, Context context)
		throws JessException {
		StringBuffer buffer =
			new StringBuffer(makeString(vvec.get(0), context));
		for (int i = 1; i < vvec.size(); i++) {
			buffer.append(' ').append(makeString(vvec.get(i), context));
		}
		return "(" + buffer.toString() + ")";
	}
}